﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using EasyStep.Business.Interfaces;
using EasyStep.TestEditor.Test;
using EasyStep.Data.Entities;
using EasyStep.TestExecution;
using EasyStep.WinForms.Controls;
using EasyStep.Attributes;
using EasyStep.TestExecution.Interfaces;
using EasyStep.TestExecution.Data;
using System.Threading;

namespace EasyStep.TestEditor.Controls.Execute
{
    public partial class ExecuteTestPanel : UserControl
    {
        private const string loading = "loading...";
        private IExecuteDataContext DataContext { get; set; }
        private TestInfo TestDetails { get; set; }

        private List<TestExecutor> CurrentlyWorking = new List<TestExecutor>();
        private List<Thread> CurrentRunningThreads;
        private Thread MainExecutionThread;

        public ExecuteTestPanel()
        {
            InitializeComponent();
        }

        public void ReInit(TestInfo test = null)
        {
            DataContext = new TestExecutionContext((ParentForm as ITestContextForm).DataContext, null, null);
            TestDetails = test != null ? test : DataContext.TestInfoManager.GetTest(DataContext.CurrentProject,DataContext.CurrentTest, true);
            brsList.DataContext = DataContext;
            btnPause.Enabled = false;
            var actions = DataContext.ActionManager.GetAll();
            int positionIndex = 0;
            foreach (TestStep step in TestDetails.Steps)
            {
                var c = AddStep(step, positionIndex++, actions);
            }
            SetControlsState(false);
        }


        private TestStepViewControl AddStep(TestStep data, int position, IEnumerable<TestAction> actions)
        {
            data.Id = data.Id != null && !data.Id.Equals(Guid.Empty) ? data.Id : Guid.NewGuid();
            var stepControl = new TestStepViewControl(data, actions, this.imagesList)
            {
                Name = string.Format("sc_{0}", data.Id),
                Dock = DockStyle.Top,
                Visible = true
            };
            PnlSteps.Controls.Add(stepControl);
            stepControl.BringToFront();
            return stepControl;
        }

        private void btnStart_Click(object sender, EventArgs e)
        {
            this.SuspendLayout();
            if (brsList.SelectedBrowsers.Any())
            {
                foreach (TestStepViewControl tStepView in PnlSteps.Controls)
                    tStepView.ReInitResult();
                MainExecutionThread = ExecuteTests(chbIsParallelWork.Checked, chkStopOnError.Checked, chkWithScreenshots.Checked, 5);
            }
            else
                MessageBox.Show("Please, select at least one browser.");
            this.ResumeLayout();
            SetControlsState(CurrentlyWorking.Count > 0);
        }

        #region [ Events ]
        void executor_OnBeforeExecuteStep(ITestExecutor executor, ExecuteEventArgs e)
        {
            this.Invoke(new MethodInvoker(() =>
            {
                DataContext.Log.Debug("event Before execute step" + (executor == null ? "" : executor.DataContext.BrowserInfo.Name));
                FindAndUpdateBrowserResult(e.TestStepResult);
            }));
        }
        void executor_OnBeforeExecuteChildStep(ITestExecutor executor, ExecuteEventArgs e)
        {
            DataContext.Log.Debug("event Before execute child step  " + (executor == null ? "" : executor.DataContext.BrowserInfo.Name));
        }

        void executor_OnAfterExecuteStep(ITestExecutor executor, ExecuteEventArgs e)
        {
            this.Invoke(new MethodInvoker(() =>
            {
                DataContext.Log.Debug("event After step execute" + (executor == null ? "" : executor.DataContext.BrowserInfo.Name));
                FindAndUpdateBrowserResult(e.TestStepResult);
            }));
        }


        void executor_OnAfterExecuteChildStep(ITestExecutor executor, ExecuteEventArgs e)
        {
            DataContext.Log.Debug("event After child step execute" + (executor == null ? "" : executor.DataContext.BrowserInfo.Name));
        }
        void executor_OnExecutionCompleted(object sender, EventArgs e)
        {
            var executor = sender as TestExecutor;
            DataContext.Log.Debug("all steps been executed:" + (executor == null ? "" : executor.DataContext.BrowserInfo.Name));
            CurrentlyWorking.Remove(executor);
            this.Invoke(new MethodInvoker(() =>
            {
                SetControlsState(CurrentlyWorking.Count > 0);
            }));
        }
        #endregion

        private class BrowserThreadData
        {
            public IWebBrowserProxy browser;
            public TestInfo testInfo;
            public bool stopOnFirstFailed;
            public bool withScreenshots;
            public int pauseLength;
        }

        private void ExecuteTestsInBrowser(object d)
        {
            var data = d as BrowserThreadData;
            var executionContext = new TestExecutionContext(DataContext, data.browser, (DataContext as IExecuteDataContext).CurrentSessionName);
            var executor = new TestExecutor(executionContext);
            CurrentlyWorking.Add(executor);
            this.Invoke(new MethodInvoker(() =>
            {
                SetControlsState(CurrentlyWorking.Count > 0);
            }));
            
            executor.OnBeforeExecuteStep += new TestStepEventHandler(executor_OnBeforeExecuteStep);
            executor.OnAfterExecuteStep += new TestStepEventHandler(executor_OnAfterExecuteStep);
            executor.OnExecutionCompleted += new EventHandler(executor_OnExecutionCompleted);
            executor.OnBeforeExecuteChildStep += new TestStepEventHandler(executor_OnBeforeExecuteChildStep);
            executor.OnAfterExecuteChildStep += new TestStepEventHandler(executor_OnAfterExecuteChildStep);
            try
            {
                executor.Run(data.testInfo, data.withScreenshots, data.stopOnFirstFailed, data.pauseLength);
            }
            catch (Exception ex)
            {
                DataContext.Log.Fatal("Test Execution Error!", ex);
                CurrentlyWorking.Remove(executor);
                executor.Dispose();
                this.Invoke(new MethodInvoker(() =>
                {
                    SetControlsState(CurrentlyWorking.Count > 0);
                }));
            }
        }

        private Thread ExecuteTests(bool parallel = true, bool stopOnFirstFailed = true, bool withScreenshots = true, int pauseLength = 50)
        {
            (DataContext as IExecuteDataContext).CurrentSessionName = string.Format("{0:yyyy_MM_dd HH_mm_ss}", DateTime.Now);
            SetControlsState(false);
            var browsers=brsList.SelectedBrowsers;
            var helpThread = new Thread(() =>
            {
                CurrentRunningThreads = new List<Thread>();
                foreach (var item in browsers)
                    if (parallel)
                    {
                        CurrentRunningThreads.Add(new Thread(ExecuteTestsInBrowser));
                        CurrentRunningThreads.Last().Start(new BrowserThreadData()
                        {
                            browser = item,
                            testInfo = TestDetails,
                            pauseLength = pauseLength,
                            stopOnFirstFailed = stopOnFirstFailed,
                            withScreenshots = withScreenshots
                        });
                    }
                    else
                        ExecuteTestsInBrowser(new BrowserThreadData()
                        {
                            browser = item,
                            testInfo = TestDetails,
                            pauseLength = pauseLength,
                            stopOnFirstFailed = stopOnFirstFailed,
                            withScreenshots = withScreenshots
                        });
            });
            helpThread.Start();
            SetControlsState(true);
            return helpThread;
        }

        private void SetControlsState(bool testIsExecuting)
        {
            pnlPlayControl.SuspendLayout();
            chbIsParallelWork.Enabled = btnStart.Visible = !testIsExecuting;
            btnStop.Visible = testIsExecuting;
            btnPause.Visible = testIsExecuting;

            pnlPlayControl.ResumeLayout();
            pnlPlayControl.Update();
            pnlPlayControl.PerformLayout();

        }

        private void FindAndUpdateBrowserResult(TestStepResult data)
        {
            var id = string.Format("sc_{0}", data.StepInfo.Id);
            var stepBrowser = PnlSteps.Controls[id] as TestStepViewControl;
            if (stepBrowser == null)
            {
                DataContext.Log.Fatal("Can't find control by name to render test step result");
                return;
            }
            stepBrowser.SetResult(data);
        }

        private void btnStop_Click(object sender, EventArgs e)
        {
            if (MainExecutionThread != null)
            {
                MainExecutionThread.Abort();
                MainExecutionThread = null;
            }
            if (CurrentRunningThreads != null)
            {
                CurrentRunningThreads.ForEach(t => t.Abort());
                CurrentRunningThreads = null;
            }

            if (CurrentlyWorking != null && CurrentlyWorking.Count > 0)
                while (CurrentlyWorking.Count > 0)
                    try
                    {
                        var item = CurrentlyWorking[0];
                        if (item == null) continue;
                        lock (item)
                        {
                            CurrentlyWorking.Remove(item);
                            item.Pause(true);
                            item.Dispose();
                        }
                    }
                    catch (Exception ex)
                    {
                        DataContext.Log.Fatal("Can't stop execution:", ex);
                    }
            SetControlsState(CurrentlyWorking.Count > 0);
        }
    }
}
